/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.common.testing;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.facebook.common.time.MonotonicClock;

/**
 *
 *
 */
public class FakeClock implements MonotonicClock {

    public interface OnTickListener {
        void onTick();
    }

    private final Map<OnTickListener, OnTickListener> otherClocks =
            new ConcurrentHashMap<OnTickListener, OnTickListener>();
    private final Map<OnTickListener, OnTickListener> listeners =
            new ConcurrentHashMap<OnTickListener, OnTickListener>();

    private long now;

    public FakeClock() {
        now = 100000;
    }

    public FakeClock(long now) {
        this.now = now;
    }

    public void incrementBy(long amountMs) {
        synchronized (this) {
            now += amountMs;
        }
        // Update other clocks before dispatching to listeners.  This is not a perfect solution, since
        // the other clocks might still dispatch events before subsequent clocks are updated.  All
        // clocks should be forced to update their time before dispatching events so all clocks are
        // guaranteed in sync, but it's better than the random order of having everything in a single
        // listeners Map where some events will be dispatched before other clocks are updated depending
        // on hashing randomness.
        for (OnTickListener otherClock : otherClocks.keySet()) {
            otherClock.onTick();
        }
        for (OnTickListener listener : listeners.keySet()) {
            listener.onTick();
        }
    }

    /**
     * Increments the clock's time gradually in several steps
     * <p/>
     * The clock will be incremented by tickMs amount repeatedly until it has progress by totalMs
     * from the given time when this call was made.
     * <p/>
     * This call is useful for working with animations in tests.
     * <p/>
     * In the end of this call, the call will progress by exactly totalMs, even if it means the last
     * tick of the clock is smaller than tickMs
     *
     * @param tickMs  the amount to tick the clock at each step
     * @param totalMs the total amount of time to elapse during the call
     */
    public void incrementInSteps(long tickMs, long totalMs) {
        long start = now;
        long end = start + totalMs;
        while (now < end) {
            incrementBy(Math.min(tickMs, end - now));
        }
    }

    @Override
    public synchronized long now() {
        return now;
    }

    /**
     * Add a clock that will get called before {@link #listeners}, in order to guarantee call order.
     */
    public void addOtherClock(OnTickListener otherClock) {
        otherClocks.put(otherClock, otherClock);
    }

    public void addListener(OnTickListener listener) {
        listeners.put(listener, listener);
    }

    public void removeListener(OnTickListener listener) {
        listeners.remove(listener);
    }
}
